package diagnostic

import (
	"mi/compiler/ast"
	"mi/internal/xpath"
)

/* internal */
type Collection interface {
	// Adds a diagnostic to this diagnostic collection.
	Add(diagnostic *Diagnostic)

	// Gets all the diagnostics that aren't associated with a file.
	GetGlobalDiagnostics() []*Diagnostic

	// If fileName is provided, gets all the diagnostics associated with that file name.
	// Otherwise, returns all the diagnostics (global and file associated) in this collection.
	GetDiagnostics(uri xpath.URI) []*Diagnostic

	// Gets a count of how many times this collection has been modified.  This value changes
	// each time 'add' is called (regardless of whether or not an equivalent diagnostic was
	// already in the collection).  As such, it can be used as a simple way to tell if any
	// operation caused diagnostics to be returned by storing and comparing the return value
	// of this method before/after the operation is performed.
	GetModificationCount() int

	/* internal */ ReattachFileDiagnostics(newFile *ast.SourceFile)
}

type diagnosticCollection struct {
	nonFileDiagnostics []*Diagnostic
	fileDiagnostics map[string][]*Diagnostic

	diagnosticsModified bool
	modificationCount int
}

func CreateDiagnosticCollection() *diagnosticCollection {
	return &diagnosticCollection{
		nonFileDiagnostics:  nil,
		fileDiagnostics:     make(map[string][]*Diagnostic),
		diagnosticsModified: false,
		modificationCount:   0,
	}
}

func (dc *diagnosticCollection) GetModificationCount() int {
	return dc.modificationCount
}

func (dc *diagnosticCollection) ReattachFileDiagnostics(newFile *ast.SourceFile) {
	for _, diagnostic := range dc.fileDiagnostics[newFile.Uri.Filename()] {
		diagnostic.File = newFile
	}
}

func (dc *diagnosticCollection) Add(diagnostic *Diagnostic) {
	if diagnostic.File != nil {
		var sourceFile = diagnostic.File
		dc.fileDiagnostics[sourceFile.Uri.Filename()] =
			append(dc.fileDiagnostics[sourceFile.Uri.Filename()], diagnostic)
	} else {
		dc.nonFileDiagnostics = append(dc.nonFileDiagnostics, diagnostic)
	}
	dc.diagnosticsModified = true
	dc.modificationCount++
}

func (dc *diagnosticCollection) GetGlobalDiagnostics() []*Diagnostic{
	dc.sortAndDeduplicate()
	return dc.nonFileDiagnostics
}

func (dc *diagnosticCollection) GetDiagnostics(uri xpath.URI) []*Diagnostic {
	dc.sortAndDeduplicate()
	if len(uri.Filename()) > 0 {
		return dc.fileDiagnostics[uri.Filename()]
	}

	var allDiagnostics []*Diagnostic
	for _, diagnostic := range dc.nonFileDiagnostics {
		allDiagnostics = append(allDiagnostics, diagnostic)
	}
	for _, diagnostics := range dc.fileDiagnostics {
		allDiagnostics = append(allDiagnostics, diagnostics ...)
	}
	return SortAndDeduplicateDiagnostics(allDiagnostics)
}

func (dc *diagnosticCollection) sortAndDeduplicate() {
	if !dc.diagnosticsModified {
		return
	}

	dc.diagnosticsModified = false
	dc.nonFileDiagnostics = SortAndDeduplicateDiagnostics(dc.nonFileDiagnostics)

	for key, diagnostics := range dc.fileDiagnostics {
		dc.fileDiagnostics[key] = SortAndDeduplicateDiagnostics(diagnostics)
	}
}
